package store.lunangangster.engine.impl.entity;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import store.lunangangster.bpmn.model.FlowElement;
import store.lunangangster.bpmn.model.Process;
import store.lunangangster.bpmn.model.SubProcess;
import store.lunangangster.engine.impl.Context;
import store.lunangangster.engine.impl.entity.mgr.VariableScope;
import store.lunangangster.engine.impl.entity.mgr.VariableScopeImpl;
import store.lunangangster.engine.impl.helper.ProcessDefinitionUtil;
import store.lunangangster.engine.impl.runtime.ProcessInstance;

public class ExecutionEntityImpl extends VariableScopeImpl implements ExecutionEntity {

  // DB property
  protected String name;
  protected String processInstanceId;
  protected String processDefinitionId;
  protected String parentId;
  protected String rootId;
  protected String modelId;
  protected Date startTime;
  protected boolean isActive;
  protected boolean isScope;

  // ####### cached
  protected String processDefinitionKey;
  protected String deploymentId;

  // parent Execution
  protected ExecutionEntity parent;
  // child Execution
  protected List<ExecutionEntity> childExecutions;
  // root ProcessInstance Execution
  protected ProcessInstance rootProcessInstance;
  // the Execution is going on what FlowElement now
  protected FlowElement currentElement;

  @Override
  public String getName() {
    return name;
  }

  @Override
  public String getProcessInstanceId() {
    return processInstanceId;
  }

  @Override
  public String getProcessDefinitionId() {
    return processDefinitionId;
  }

  @Override
  public String getParentId() {
    return parentId;
  }

  @Override
  public String getRootId() {
    return rootId;
  }

  @Override
  public String getProcessDefinitionKey() {
    if (processDefinitionKey == null) {
      ProcessDefinitionUtil.getExecutionEntityWithProcessDefinitionProperty(this);
    }
    return processDefinitionKey;
  }

  @Override
  public String getProcessInstanceName() {
    if (parentId == null) {
      return name;
    }
    return getRootProcessInstance().getProcessInstanceName();
  }

  @Override
  public String getDeploymentId() {
    return deploymentId;
  }

  @Override
  public Date getStartTime() {
    return startTime;
  }

  @Override
  public String getModelId() {
    return modelId;
  }

  @Override
  public boolean isActive() {
    return isActive;
  }

  @Override
  public boolean getIsActive() {
    return isActive;
  }

  // lazy load
  @Override
  public ExecutionEntity getParent() {
    if (parent == null && parentId != null) {
      parent = Context.getProcessEngineConfiguration().getExecutionEntityManager().selectById(parentId);
    }
    return parent;
  }

  @Override
  public List<ExecutionEntity> getChildExecutions() {
    ensureExecutionsInitialized();
    return childExecutions;
  }

  @Override
  public void addChildExecutions(ExecutionEntity execution) {
    ensureExecutionsInitialized();
    childExecutions.add(execution);
  }

  @Override
  public ProcessInstance getRootProcessInstance() {
    if (rootProcessInstance == null) {
      rootProcessInstance = Context.getProcessEngineConfiguration()
          .getExecutionEntityManager().selectById(rootId);
    }
    return rootProcessInstance;
  }

  @Override
  public FlowElement getCurrentFlowElement() {
    if (currentElement == null) {
      Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
      currentElement = process.getFlowElements().get(modelId);
      if (currentElement == null) {
        ExecutionEntity parentExecution = this.getParent();
        if (parentExecution == null) {
          return null;
        }
        FlowElement flowElement = process.getFlowElements().get(parentExecution.getModelId());
        if (flowElement instanceof SubProcess) {
          currentElement = ((SubProcess) flowElement).getFlowElements().get(modelId);
        }
      }
    }
    return currentElement;
  }

  @Override
  public void setName(String name) {
    this.name = name;
  }

  @Override
  public void setProcessInstanceId(String id) {
    this.processInstanceId = id;
  }

  @Override
  public void setProcessDefinitionId(String id) {
    this.processDefinitionId = id;
  }

  @Override
  public void setProcessDefinitionKey(String processDefinitionKey) {
    this.processDefinitionKey = processDefinitionKey;
  }

  @Override
  public void setDeploymentId(String deploymentId) {
    this.deploymentId = deploymentId;
  }

  @Override
  public void setStartTime(Date startTime) {
    this.startTime = startTime;
  }

  @Override
  public void setModelId(String modelId) {
    this.modelId = modelId;
  }

  @Override
  public void setParentId(String id) {
    this.parentId = id;
  }

  @Override
  public void setRootId(String id) {
    this.rootId = id;
  }

  @Override
  public void setParent(ExecutionEntity parentExecution) {
    this.parent = parentExecution;
    if (parentExecution != null) {
      this.parentId = parentExecution.getId();
    }
  }

  @Override
  public void setRootProcessInstance(ProcessInstance rootProcessInstance) {
    this.rootProcessInstance = rootProcessInstance;
    if (rootProcessInstance != null) {
      this.rootId = rootProcessInstance.getId();
    }
  }

  @Override
  public void setCurrentFlowElement(FlowElement flowElement) {
    this.currentElement = flowElement;
    this.modelId = flowElement.getId();
  }

  @Override
  public void setActive(boolean isActive) {
    this.isActive = isActive;
  }

  @Override
  public void setIsActive(boolean isActive) {
    this.isActive = isActive;
  }

  private void ensureExecutionsInitialized() {
    if (childExecutions == null) {
      childExecutions = Context.getProcessEngineConfiguration().getExecutionEntityManager().selectChildExecutionByParentId(id);
      if (childExecutions == null) {
        childExecutions = new ArrayList<>();
      }
    }
  }

  @Override
  public Object getPersistState() {
    Map<String, Object> persistState = new HashMap<>();
    persistState.put("name", name);
    persistState.put("processInstanceId", processInstanceId);
    persistState.put("processDefinitionId", processDefinitionId);
    persistState.put("parentId", parentId);
    persistState.put("rootId", rootId);
    persistState.put("modelId", modelId);
    persistState.put("startTime", startTime);
    return persistState;
  }

  @Override
  public ExecutionEntity getExecution() {
    return this;
  }

  @Override
  public List<VariableEntity> loadVariableEntities() {
    return Context.getProcessEngineConfiguration().getVariableEntityManager().selectVariableByExecutionId(id);
  }

  @Override
  public VariableScope getParentVariableScope() {
    return getParent();
  }

  @Override
  public void setVariableEntityPointer(VariableEntity variableEntity) {
    variableEntity.setExecutionId(id);

    if (processInstanceId != null) {
      variableEntity.setProcessInstanceId(processInstanceId);
    } else {
      variableEntity.setProcessInstanceId(id);
    }
  }

  @Override
  public boolean isScope() {
    return isScope;
  }

  @Override
  public void setScope(boolean isScope) {
    this.isScope = isScope;
  }
}
